//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#ifndef __ELASTOS_PAGING_H__
#define __ELASTOS_PAGING_H__

#include "../../../../major/core/inc/_hal/arm/register.h"

// Page macros
#define PAGE_SHIFT                  (12)
#define PAGEDIR_SHIFT               (20)
#define PAGE_TO_PAGEDIR_SHIFT       (PAGEDIR_SHIFT - PAGE_SHIFT)

#define PAGE_SIZE                   (1u << PAGE_SHIFT)
#define PAGEDIR_SIZE                (1u << PAGEDIR_SHIFT)

#define PAGE_MASK                   (~(PAGE_SIZE - 1))
#define PAGEDIR_MASK                0xfff00000
#define PAGETAB_MASK                0xfffff000

#define PAGE_OFFSET(va)             ((uint32_t)(va) & (PAGE_SIZE - 1))
#define PAGETAB_INDEX(va)           (((uint32_t)(va) >> PAGE_SHIFT) & 0xff)
#define PAGEDIR_INDEX(va)           ((uint32_t)(va) >> PAGEDIR_SHIFT)

#define SECTION_SHIFT               (20)
#define SECTION_SIZE                (1u << SECTION_SHIFT)

// page directory entry
#define PAGEDIRENTRY_TYPE_MASK      0x00000003
#define PAGEDIRENTRY_TYPE_FAULT     0x00000000
#define PAGEDIRENTRY_TYPE_PAGE      0x00000001
#define PAGEDIRENTRY_TYPE_SECTION   0x00000002
#define PAGEDIRENTRY_TYPE_RESERVED  0x00000003

#define PAGEDIRENTRY_SECTION_C      0x00000008  // section cacheable
#define PAGEDIRENTRY_SECTION_B      0x00000004  // section bufferable
#define PAGEDIRENTRY_SECTION_AP_WR  0x00000c00  // read & write in user mode
#define PAGEDIRENTRY_SECTION_AP_RO  0x00000800  // read only in user mode
#define PAGEDIRENTRY_SECTION_AP_NA  0x00000400  // not accessable in user mode

#define COARSE_PAGETAB_BASEADDR_MASK    0xfffffc00
#define SECTION_BASEADDR_MASK           0xfff00000
#define FINE_PAGETAB_BASEADDR_MASK      0xfffff000

#define PAGEDIRENTRY_DOMAIN(domain) ((domain) << 5)
#define PAGEDIRENTRY_BACKWORD       0x00000010  // for backword compatibility?

// page table entry
#define PAGETABENTRY_TYPE_MASK      0x00000003
#define PAGETABENTRY_TYPE_FAULT     0x00000000
#define PAGETABENTRY_TYPE_LARGE     0x00000001  // 64K page
#define PAGETABENTRY_TYPE_SMALL     0x00000002  // 4K page
#define PAGETABENTRY_TYPE_RESERVE   0x00000003

#define PAGETABENTRY_AP_MASK        0x00000ff0
#define PAGETABENTRY_AP_WR          0x00000ff0  // all subpages is writeable
                                                //  in user mode
#define PAGETABENTRY_AP_RO          0x00000aa0  // all subpages is read only
                                                //  in user mode
#define PAGETABENTRY_AP_NA          0x00000550  // all subpages is no access
                                                //  in user mode
#define PAGETABENTRY_C              0x00000008  // cacheable
#define PAGETABENTRY_B              0x00000004  // bufferable

// domain
#define DOMAIN_USER     1
#define DOMAIN_IO       1
#define DOMAIN_KERNEL   1

// Any access generates a Domain fault
#define DOMAIN_CONTROL_NA           0x0
// checked against the permission bits in Section or Page descriptor
#define DOMAIN_CONTROL_CLIENT       0x1
// Reserved. like No Access Mode
#define DOMAIN_CONTROL_RESERVED     0x2
// Accesses are NOT checked against the access Permission bits
#define DOMAIN_CONTROL_MANAGER      0x3

#define SET_DOMAIN(field, control)  ((control) << ((field) << 1))

// Convertion
#define NUMBEROF_PAGES(size)        ((size) >> PAGE_SHIFT)
#define PHYSADDR_TO_PAGENO(pa)      ((pa) >> PAGE_SHIFT)
#define VIRTADDR_TO_PAGENO(va)      PHYSADDR_TO_PAGENO(PHYSADDR(va))
#define PAGENO_TO_PHYSADDR(no)      ((no) << PAGE_SHIFT)
#define PAGENO_TO_VIRTADDR(no)      VIRTADDR(PAGENO_TO_PHYSADDR(no))

#define NUMBEROF_PAGETABS(size)     ((size) >> PAGEDIR_SHIFT)
#define PHYSADDR_TO_PAGETABNO(pa)   ((pa) >> PAGEDIR_SHIFT)
#define VIRTADDR_TO_PAGETABNO(va)   PHYSADDR_TO_PAGETABNO(PHYSADDR(va))
#define PAGETABNO_TO_PHYSADDR(no)   ((no) << PAGEDIR_SHIFT)
#define PAGETABNO_TO_VIRTADDR(no)   VIRTADDR(PAGETABNO_TO_PHYSADDR(no))
#define VIRTPAGENO_TO_VIRTADDR(no)  ((no) << PAGE_SHIFT)

#define PAGENO_TO_PAGETABNO(no)     ((no) >> PAGE_TO_PAGEDIR_SHIFT)

const uint_t c_uMaxNumberOfPages    = NUMBEROF_PAGES(0xffffffff) + 1;

#define IS_VALID_PAGENO(no)         ((no) < c_uMaxNumberOfPages)

const uint_t c_uKernelBasePageNo    =
        PHYSADDR_TO_PAGENO((physaddr_t)KERNEL_BASE);

const uint_t c_uNumberOfKernelPages =
        c_uMaxNumberOfPages - c_uKernelBasePageNo;

const uint_t c_uKernelPhysBasePageNo =
        PHYSADDR_TO_PAGENO(bsp.paKernelPhysBase);

#define PHYSPAGENO_TO_VIRTPAGENO(no) \
        ((no) + c_uKernelBasePageNo - c_uKernelPhysBasePageNo)

#define VIRTPAGENO_TO_PHYSPAGENO(no) \
        ((no) - c_uKernelBasePageNo + c_uKernelPhysBasePageNo)

typedef struct PageDirEntry {
    uint32_t uValue;
} PageDirEntry;

typedef struct PageTabEntry {
    uint32_t uValue;
} PageTabEntry;

const size_t c_uSizeOfPageDir           = 0x4000;
const size_t c_uSizeOfPageTab           = 0x400;

// Number of entries
const uint_t c_uNumberOfPageDirEntries  =
        c_uSizeOfPageDir / sizeof(PageDirEntry);
const uint_t c_uNumberOfPageTabEntries  =
        c_uSizeOfPageTab / sizeof(PageTabEntry);

const uint_t c_cPageDirEntries = c_uNumberOfPageDirEntries;
const uint_t c_cPageTabEntries = c_uNumberOfPageTabEntries;

EXTERN PageDirEntry g_initPageDir[c_uNumberOfPageDirEntries];

// Coprocessor 15 Register 1
#define CR1_M   __32BIT(0)      // MMU Enable
#define CR1_A   __32BIT(1)      // Alignment Fault enable
#define CR1_C   __32BIT(2)      // Cache Enable
#define CR1_W   __32BIT(3)      // Write buffer enable
#define CR1_LDP (__32BIT(6) | __32BIT(5) | __32BIT(4))  // L, D & P bit
#define CR1_B   __32BIT(7)      // 1 = Big-endian, 0 = Little-endian
#define CR1_S   __32BIT(8)      // System protection
#define CR1_R   __32BIT(9)      // System protection
#define CR1_I   __32BIT(12)     // I Cache Enable
#define CR1_V   __32BIT(13)     // Hign vector: 0xffff0000
#define CR1_RR  __32BIT(14)     // Round-Robin replacement

#if defined(_xscale)
#define RESERVED_CACHEABLE_REGION   0xffe00000
#endif

#if defined(_GNUC)

INLINE void FlushCache()
{
    uint32_t uFlags = SaveFlagsAndCli();

#if defined(_arm720)

    ASM("mov    r0, #0;"
        "mcr    p15, 0, r0, c7, c7, 0;"
        : : :"r0");
#elif defined(_arm920)

    ASM("mov    r1, #3 << 5;"               // 4 segments
        "1:"
        "orr    r2, r1, #63 << 26;"         // 64 entries
        "2:"
        "mcr    p15, 0, r2, c7, c14, 2;"    // clean & invalidate D index
        "subs   r2, r2, #1 << 26;"
        "bcs    2b;"                        // entries 63 to 0
        "subs   r1, r1, #1 << 5;"
        "bcs    1b;"                        // segments 3 to 0
        "mov    r0, #0;"
        "mcr    p15, 0, r0, c7, c10, 4;"    // drain write buffer
        "mcr    p15, 0, r0, c7, c5, 0;"     // BUG: need invalidate I cache?
        : : :"r0", "r1", "r2");

#elif defined(_arm926)

    ASM("1:"
        "mrc    p15, 0, pc, c7, c14, 3;"	// Test and Clean and Invalidate D-Cache
        "bne    1b;"
        "mcr    p15, 0, r0, c7, c10, 4;"    // drain write buffer
        "mcr    p15, 0, r0, c7, c5, 0;"		// Invalidate I-Cache
        : : :"r0");

#elif defined(_xscale)

    static int s_switch;

    s_switch ^= 32768;

    // Global Clean or Invalidate The Data Cache
    ASM("mov    r0, #1024;"
        "1:"
        "mcr    p15, 0, %0, c7, c2, 5;"     // Allocate a line at the virtual
                                            //  address specified by %0
        "add    %0, %0, #32;"               // Increment the address in %0 to
                                            // the next cache line
        "subs   r0, r0, #1;"                // Decrement loop count
        "bne    1b;"
        : :"r"(RESERVED_CACHEABLE_REGION + s_switch) :"r0");

    ASM("mov    r0, #0;"
        "mcr    p15, 0, r0, c7, c10, 4;"    // drain write (& fill) buffer
        : : :"r0");

    ASM("mcr    p15, 0, r0, c7, c7, 0;"     // invalidate D & I cache
        : : :"r0");

    ASM("mrc    p15, 0, r0, c2, c0, 0;"     // arbitrary read of cp15
        "mov    r0, r0;"                    // wait for it
        "sub    pc, pc, #4;"
        : : :"r0");

#elif defined(_arm1176jzfs)
    ASM("	mov	r0, #0;"
        "mcr	p15, 0, r0, c7, c14, 0;"		//@ D cache clean+invalidate
        "mcr	p15, 0, r0, c7, c5, 0;"		//@ I+BTB cache invalidate
        : : :"r0");

#else
#error Unknown architecture variant
#endif

    RestoreIF(uFlags);
}

INLINE void FlushAllTlbs()
{
#if defined(_arm720)

    ASM("mov    r0, #0;"
        "mcr    p15, 0, r0, c8, c7, 0;"     // invalidate unified TLB
        : : :"r0");
#elif defined(_arm920) || defined(_xscale) || defined(_arm926)

    ASM("mov    r0, #0;"
        "mcr    p15, 0, r0, c7, c10, 4;"    // drain write buffer
        "mcr    p15, 0, r0, c8, c7, 0;"     // invalidate I & D TLBs
        : : :"r0");

#elif defined(_arm1176jzfs)

    ASM("mov    r0, #0;"
	"mcr	p15, 0, r0, c8, c7, 0;"	// flush I+D TLBs
	: : :"r0");

#else
#error Unknown architecture variant
#endif
}

INLINE void FlushTlb(virtaddr_t va)
{
#if defined(_arm720)

    ASM("mcr    p15, 0, %0, c8, c7, 1;"
        : :"r"(va));
#elif defined(_arm920) || defined(_xscale) || defined(_arm926)

    ASM("mov    r2, #0;"
        "mcr    p15, 0, r2, c7, c10, 4;"    // drain write buffer
        : : :"r2");

    // BUG: need invalidate I TLB in any case?
    ASM("mcr    p15, 0, %0, c8, c6, 1;"     // invalidate D TLB
        "mcr    p15, 0, %0, c8, c5, 1;"     // invalidate I TLB
        : :"r"(va));

#elif defined(_arm1176jzfs)

    ASM("mov    r2, #0;"
        "mcr    p15, 0, r2, c7, c10, 4;"    // drain write buffer
        : : :"r2");

    ASM("mcr p15, 0, %0, c8, c7, 1;"     // TLB invalidate MVA
        : :"r"(va));

    ASM("mov    r2, #0;"
        "mcr	p15, 0, r2, c7, c5, 6;"    // @ flush BTAC/BTB
        "mcr	p15, 0, r2, c7, c10, 4;"    // @ data synchronization barrier
        "mcr	p15, 0, r2, c7, c5, 4;"    // @ prefetch flush
        : : :"r2");

#else
#error Unknown architecture variant
#endif
}

INLINE void SetPageDirAddress(physaddr_t pagedir)
{
    ASM("mcr    p15, 0, %0, c2, c0, 0;"
        : :"r"(pagedir));
}

INLINE physaddr_t GetPageDirAddress()
{
    physaddr_t pagedir;
    ASM("mrc    p15, 0, %0, c2, c0, 0;"
        :"=r"(pagedir));
    return pagedir;
}

INLINE uint32_t GetFaultStatus()
{
    uint32_t fs;

    ASM("mrc    p15, 0, %0, c5, c0, 0;"
        :"=r"(fs));

    return fs;
}

INLINE void SetFaultStatus(uint32_t fs)
{
    ASM("mcr    p15, 0, %0, c5, c0, 0;"
        : :"r"(fs));
}

INLINE virtaddr_t GetFaultAddress()
{
    virtaddr_t fa;

    ASM("mrc    p15, 0, %0, c6, c0, 0;"
        :"=r"(fa));

    return fa;
}

INLINE void SetFaultAddress(virtaddr_t fa)
{
    ASM("mcr    p15, 0, %0, c6, c0, 0;"
        : :"r"(fa));
}

INLINE void EnableMMU(uint32_t cr3, physaddr_t pagedir, uint32_t cr1)
{
//    FlushCache();
    FlushAllTlbs();

    ASM("mcr    p15, 0, %0, c3, c0, 0;"
        : :"r"(cr3));

    ASM("mcr    p15, 0, %0, c2, c0, 0;"
        : :"r"(pagedir));

    ASM("mcr    p15, 0, %0, c1, c0, 0;"
        "nop;"
        "nop;"
        : :"r"(cr1));

#if defined(_xscale)
    ASM("mrc    p15, 0, r0, c2, c0, 0;" // arbitrary read of cp15
        "mov    r0, r0;"                // wait for it
        "sub    pc, pc, #4;"
        : : :"r0");
#endif
}

INLINE void DisableMMU()
{
    FlushCache();

    ASM("mcr    p15, 0, %0, c1, c0, 0;"
        "nop;"
        "nop;"
        : :"r"(0));

#if defined(_xscale)
    ASM("mrc    p15, 0, r0, c2, c0, 0;" // arbitrary read of cp15
        "mov    r0, r0;"                // wait for it
        "sub    pc, pc, #4;"
        : : :"r0");
#endif
}
#elif defined(_EVC)

EXTERN_C void FlushCache();

EXTERN_C void FlushAllTlbs();
EXTERN_C void FlushTlb(virtaddr_t va);

EXTERN_C void SetPageDirAddress(physaddr_t pagedir);
EXTERN_C uint32_t GetFaultStatus();
EXTERN_C void SetFaultStatus(uint32_t fs);
EXTERN_C virtaddr_t GetFaultAddress();
EXTERN_C void SetFaultAddress(virtaddr_t fa);

EXTERN_C void EnableMMU(uint32_t cr3, physaddr_t pagedir, uint32_t cr1);
EXTERN_C void DisableMMU();

#else

#error Unknown C++ compiler

#endif // defined(_GNUC)

INLINE void FlushCacheByRange(uint_t uBase, uint_t uLimit)
{
    // TODO: Need optimizing
    FlushCache();
}

INLINE void FlushInstructionCache(uint_t uBase, uint_t uLimit)
{
#if defined(_arm720)

    //do nothing
#elif defined(_arm920)

    // TODO: Need optimizing
    FlushCache();
#elif defined(_arm926)

    // TODO: Need optimizing
    FlushCache();
#elif defined(_xscale)

    // TODO: Need optimizing
    FlushCache();

#elif defined(_arm1176jzfs)
    // TODO: Need optimizing
    FlushCache();

#else
#error Unknown architecture variant
#endif
}

#endif //__ELASTOS_PAGING_H__
